package com.tmt.im.wallet.service;

import com.tmt.im.common.enums.IMAuthorityEnum;
import com.tmt.im.common.enums.RedPackageType;
import com.tmt.im.common.pojo.RedPackageSkinPoJo;
import com.tmt.im.jpa.entity.SystemParam;
import com.tmt.im.jpa.entity.UploadedFile;
import com.tmt.im.jpa.repository.SystemParamRepo;
import com.tmt.im.jpa.repository.UploadedFileRepo;
import com.tmt.im.wallet.controler.RedPackageSkinController;
import com.tmt.im.wallet.controler.vo.UpdateSkinRequest;
import com.tmt.im.wallet.helper.IgniteQueryHelper;
import com.tmt.im.wallet.helper.PrincipalHelper;
import com.tmt.springboot.common.helper.IdSnowFlake;
import com.tmt.springboot.common.restful.ExecuteResult;
import com.tmt.springboot.common.restful.IExecuteResult;
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.RemoveObjectArgs;
import io.minio.http.Method;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.ignite.Ignite;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.Principal;
import java.sql.Timestamp;
import java.text.MessageFormat;
import java.time.OffsetDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

/**
 * 红包服务
 *
 * @Description TODO
 * @Author wuyi
 * @Date 2024/9/02 12:13
 * @Version 1.0
 **/
@Slf4j
@Service
public class RedPackageSkinService extends BaseService {

    private final ApplicationContext applicationContext;

    private final MinioClient minioClient;

    private final String bucket;

    private final String apiUrlPrefix;

    private final SystemParamRepo systemParamRepo;

    private final UploadedFileRepo uploadedFileRepo;

    private String skinUrlPrefix;


    public RedPackageSkinService(ApplicationContext applicationContext,
                                 Ignite ignite,
                                 @Value("${minio.bucket}")
                                 String bucket,
                                 @Value("${api-url-prefix}")
                                 String apiUrlPrefix,
                                 MinioClient minioClient,
                                 SystemParamRepo systemParamRepo,
                                 UploadedFileRepo uploadedFileRepo) {
        super(ignite);
        this.applicationContext = applicationContext;
        this.minioClient = minioClient;
        this.bucket = bucket;
        this.apiUrlPrefix = apiUrlPrefix;
        this.systemParamRepo = systemParamRepo;
        this.uploadedFileRepo = uploadedFileRepo;
    }

    public void getSkin(Long skinId, String extendName, HttpServletResponse response, Principal principal) {
        RedPackageSkinPoJo skin = skinCache.get(skinId);
        if (skin == null) {
            return;
        }

        if (!StringUtils.hasText(extendName)) {
            String[] infos = skin.getUrl().split("\\.");
            extendName = infos[infos.length - 1];
        }

        //文件名称。
        String fileName = MessageFormat.format("{0}/{1}.{2}", skin.getType().toLowerCase(), "" + skinId, extendName);
        String skinUrl = null;
        try {
            //获取用户头像文件访问路径。
            skinUrl = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                    .method(Method.GET)
                    .bucket(this.bucket)
                    .object(fileName)
                    .build());
        } catch (Exception ex) {
            log.warn(ex.getMessage(), ex);
        }

        BufferedImage bufferedImage = null;
        try {
            assert skinUrl != null;
            URL url = new URL(skinUrl);
            bufferedImage = ImageIO.read(url);
        } catch (Exception ignored) {
        }

        InputStream inputStream = null;
        ByteArrayOutputStream outputStream = null;
        if (bufferedImage != null) {
            try {
                outputStream = new ByteArrayOutputStream();
                ImageIO.write(bufferedImage, extendName.toUpperCase(), outputStream);
                inputStream = new ByteArrayInputStream(outputStream.toByteArray());
            } catch (Exception ignored) {
            }
        } else {
            String defaultAvatar = MessageFormat.format("/images/default-{0}-skin.png", skin.getType().toLowerCase());
            extendName = "png";
            inputStream = RedPackageSkinService.class.getResourceAsStream(defaultAvatar);
            if (inputStream == null) {
                defaultAvatar = "/images/default-red-package-skin.png";
                inputStream = RedPackageSkinService.class.getResourceAsStream(defaultAvatar);
            }
        }

        if (inputStream != null) {
            String contentType = MediaType.IMAGE_GIF_VALUE;
            if ("png".equals(extendName)) {
                contentType = MediaType.IMAGE_PNG_VALUE;
            } else if ("jpg".equals(extendName)) {
                contentType = MediaType.IMAGE_JPEG_VALUE;
            }

            try {
                // 清空response
                response.reset();
                // 设置response的Header
                response.setCharacterEncoding("UTF-8");
                //Content-Disposition的作用：告知浏览器以何种方式显示响应返回的文件，用浏览器打开还是以附件的形式下载到本地保存
                //attachment表示以附件方式下载 inline表示在线打开 "Content-Disposition: inline; filename=文件名.mp3"
                // filename表示文件的默认名称，因为网络传输只支持URL编码的相关支付，因此需要将文件名URL编码后进行传输,前端收到后需要反编码才能获取到真正的名称
                response.addHeader("Content-Disposition", "inline;filename=" + URLEncoder.encode(skinId + "_skin." + extendName, StandardCharsets.UTF_8));
                // 告知浏览器文件的大小
                response.addHeader("Content-Length", "" + inputStream.available());
                response.setContentType(contentType);
                try (OutputStream output = new BufferedOutputStream(response.getOutputStream())) {
                    output.write(inputStream.readAllBytes());
                    output.flush();
                } catch (Exception ignored) {
                }
            } catch (Exception ignored) {
            } finally {
                try {
                    inputStream.close();
                    if (outputStream != null) {
                        outputStream.close();
                    }
                } catch (Exception ignored) {
                }
            }
        }
    }

    /**
     * 新增红包皮肤
     *
     * @param type      上传皮肤的类型
     * @param newImage
     * @param principal
     * @return
     */
    public IExecuteResult<RedPackageSkinPoJo> addSkin(RedPackageType type, String name, MultipartFile newImage, Principal principal) {
        if (newImage == null) {
            return ExecuteResult.failure("上传文件不能为null", null);
        }

        Long currentUserId = PrincipalHelper.getCurrentUserId(principal);

        try {
            String fileName = newImage.getOriginalFilename();
            assert fileName != null;

            SystemParam param = systemParamRepo.findOneByParamCode("valid_image_types");
            List<String> validImageTypes = Arrays.stream(param.getParamValue().split(",")).map(String::trim).toList();

            //获取文件扩展名称。
            int index = fileName.lastIndexOf(".");
            String ext = index > 0 && index + 1 < fileName.length() ? fileName.substring(index) : "";
            if (validImageTypes.contains(ext.substring(1))) {
                throw new Exception("不支持上传的文件类型");
            }

            String contentType = MediaType.IMAGE_GIF_VALUE;
            if (ext.toLowerCase().endsWith("png")) {
                contentType = MediaType.IMAGE_PNG_VALUE;
            } else if (ext.toLowerCase().endsWith("jpg")) {
                contentType = MediaType.IMAGE_JPEG_VALUE;
            }

            Long id = IdSnowFlake.getId();
            //文件名称。
            fileName = MessageFormat.format("{0}/{1}{2}", type.name().toLowerCase(), "" + id, ext);
            //将文件保存到minio中。
            minioClient.putObject(PutObjectArgs.builder()
                    .bucket(this.bucket)        // 指定 Bucket
                    .contentType(contentType)   // 指定 Content Type
                    .object(fileName)           // 指定文件的路径
                    .stream(newImage.getInputStream(), newImage.getSize(), -1) // 文件的 Inputstream 流
                    .build());

            if (!StringUtils.hasText(skinUrlPrefix)) {
                RedPackageSkinController controller = applicationContext.getBean(RedPackageSkinController.class);
                RequestMapping ann = null;
                if (controller.getClass().getSimpleName().endsWith(".RedPackageSkinController")) {
                    ann = controller.getClass().getAnnotation(RequestMapping.class);
                } else {
                    ann = ((Class<?>) controller.getClass().getGenericSuperclass()).getAnnotation(RequestMapping.class);
                }
                assert ann != null;
                skinUrlPrefix = ann.value()[0];
                skinUrlPrefix = skinUrlPrefix.replace("${api-url-prefix}", apiUrlPrefix);
            }

            String skinUri = MessageFormat.format("{0}/show/{1}{2}", skinUrlPrefix, "" + id, ext);
            skinUri = skinUri.replaceAll("//", "/");

            RedPackageSkinPoJo skin = RedPackageSkinPoJo.builder()
                    .id(id)
                    .name(name)
                    .type(type.name())
                    .url(skinUri)
                    .creatorId(currentUserId)
                    .version(0L)
                    .createTime(new Timestamp(System.currentTimeMillis()))
                    .insert(true)
                    .build();

            skinCache.put(skin.getKey(), skin);

            UploadedFile uploadedFile = new UploadedFile();
            uploadedFile.setId(id);
            uploadedFile.setType(3);
            uploadedFile.setContentType(contentType);
            uploadedFile.setFileName(newImage.getOriginalFilename());
            uploadedFile.setSuffixType(ext.substring(1));
            uploadedFile.setFileUri(skinUri);
            uploadedFile.setFileSize(newImage.getSize());
            uploadedFile.setVersion(0L);
            uploadedFile.setCreateTime(new Timestamp(System.currentTimeMillis()));

            uploadedFileRepo.save(uploadedFile);

            return ExecuteResult.success("上传成功", skin);
        } catch (Exception ex) {
            return ExecuteResult.failure(ex.getMessage(), null);
        }
    }

    public IExecuteResult<List<RedPackageSkinPoJo>> getSkinList(RedPackageType type, Principal principal) {
        String sql = MessageFormat.format("select * from {0} where type = ? order by createTime desc", RedPackageSkinPoJo.CACHE_NAME);
        List<RedPackageSkinPoJo> list = IgniteQueryHelper.findAllBy(sql, new Object[]{type.name()}, RedPackageSkinPoJo.class, skinCache);
        return ExecuteResult.success(list);
    }

    public IExecuteResult<RedPackageSkinPoJo> updateSkin(UpdateSkinRequest request, Principal principal) {
        try {
            RedPackageSkinPoJo skinPoJo = skinCache.get(request.getSkinId());
            if (skinPoJo == null) {
                return ExecuteResult.failure(MessageFormat.format("指定ID(={0})的皮肤不存在", request.getSkinId()), null);
            }

            SystemParam param = systemParamRepo.findOneByParamCode("valid_image_types");
            List<String> validImageTypes = Arrays.stream(param.getParamValue().split(",")).map(String::trim).toList();

            //获取文件扩展名称。
            String fileName = request.getNewSkin().getName();

            int index = fileName.lastIndexOf(".");
            String ext = index > 0 && index + 1 < fileName.length() ? fileName.substring(index) : "";
            if (validImageTypes.contains(ext.substring(1))) {
                throw new Exception("不支持上传的文件类型");
            }

            String contentType = MediaType.IMAGE_GIF_VALUE;
            if (ext.toLowerCase().endsWith("png")) {
                contentType = MediaType.IMAGE_PNG_VALUE;
            } else if (ext.toLowerCase().endsWith("jpg")) {
                contentType = MediaType.IMAGE_JPEG_VALUE;
            }

            //文件名称。
            fileName = MessageFormat.format("{0}/{1}{2}", request.getType().name().toLowerCase(), "" + request.getSkinId(), ext);
            //将文件保存到minio中。
            minioClient.putObject(PutObjectArgs.builder()
                    .bucket(this.bucket)        // 指定 Bucket
                    .contentType(contentType)// 指定 Content Type
                    .object(fileName)        // 指定文件的路径
                    .stream(request.getNewSkin().getInputStream(), request.getNewSkin().getSize(), -1) // 文件的 Inputstream 流
                    .build());

            if (!StringUtils.hasText(skinUrlPrefix)) {
                RedPackageSkinController controller = applicationContext.getBean(RedPackageSkinController.class);
                RequestMapping ann = null;
                if (controller.getClass().getSimpleName().endsWith(".RedPackageSkinController")) {
                    ann = controller.getClass().getAnnotation(RequestMapping.class);
                } else {
                    ann = ((Class<?>) controller.getClass().getGenericSuperclass()).getAnnotation(RequestMapping.class);
                }
                assert ann != null;
                skinUrlPrefix = ann.value()[0];
                skinUrlPrefix = skinUrlPrefix.replace("${api-url-prefix}", apiUrlPrefix);
            }

            String skinUri = MessageFormat.format("{0}/show/{1}{2}", skinUrlPrefix, "" + request.getSkinId(), ext);
            skinUri = skinUri.replaceAll("//", "/");

            skinPoJo.setName(request.getName());
            skinPoJo.setUrl(skinUri);
            skinPoJo.setType(request.getType().name());
            skinPoJo.setCreatorId(PrincipalHelper.getCurrentUserId(principal));
            skinPoJo.setCreateTime(new Timestamp(System.currentTimeMillis()));
            skinPoJo.setVersion(skinPoJo.getVersion() + 1);
            skinPoJo.setInsert(false);

            skinCache.put(skinPoJo.getKey(), skinPoJo);

            UploadedFile uploadedFile = uploadedFileRepo.findById(request.getSkinId()).orElse(null);
            if (uploadedFile == null){
                uploadedFile = new UploadedFile();
                uploadedFile.setId(request.getSkinId());
                uploadedFile.setType(3);
                uploadedFile.setVersion(0L);
            }
            uploadedFile.setContentType(contentType);
            uploadedFile.setFileName(request.getNewSkin().getOriginalFilename());
            uploadedFile.setSuffixType(ext.substring(1));
            uploadedFile.setFileUri(skinUri);
            uploadedFile.setFileSize(request.getNewSkin().getSize());
            uploadedFile.setCreateTime(new Timestamp(System.currentTimeMillis()));

            uploadedFileRepo.save(uploadedFile);

            return ExecuteResult.success(skinPoJo);
        } catch (Exception ex) {
            return ExecuteResult.failure(ex.getMessage(), null);
        }
    }

    /**
     * 删除红包皮肤
     *
     * @param skinId
     * @param principal
     * @return
     */
    public IExecuteResult<Boolean> deleteSkin(Long skinId, Principal principal) {
        RedPackageSkinPoJo skin = skinCache.get(skinId);
        if (skin == null) {
            return ExecuteResult.failure("指定ID的红包皮肤不存在", null);
        }

        Long currentUserId = PrincipalHelper.getCurrentUserId(principal);
        boolean hasAuthenticated = hasAuthenticated(principal, List.of(IMAuthorityEnum.IM_AUTH_ALL, IMAuthorityEnum.IM_AUTH_SESSION_ALL));
        if (Objects.equals(currentUserId, skin.getCreatorId()) || hasAuthenticated) {
            //清除缓存和数据库
            skinCache.remove(skinId);
            //从minio中删除目标文件.
            try {
                String fileName = MessageFormat.format("{0}/{1}{2}.png", skin.getType().toLowerCase(), "" + skinId);
                minioClient.removeObject(RemoveObjectArgs.builder()
                        .bucket(this.bucket)        // 指定 Bucket
                        .object(fileName)           // 指定文件的路径
                        .build());
            } catch (Exception ex) {
                log.debug(ex.getMessage(), ex);
            }
            return ExecuteResult.success(true);
        }

        return ExecuteResult.failure("无权删除该红包皮肤", false);

    }
}
